/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/math.hpp>

namespace baja { namespace graphics { namespace dx11_1 { namespace details {

inline void
computeTangentVector(
    const m::point3d& pointA, 
    const m::point3d& pointB, 
    const m::point3d& pointC, 
    const texture_coordinate& uvA, 
    const texture_coordinate& uvB, 
    const texture_coordinate& uvC, 
    m::vector3d* sVector,
    m::vector3d* tVector
    )
{
    if (sVector != NULL && tVector != NULL)
    {
        float32 x1 = pointB.x - pointA.x;
        float32 y1 = pointB.y - pointA.y;
        float32 z1 = pointB.z - pointA.z;

        float32 x2 = pointC.x - pointA.x;
        float32 y2 = pointC.y - pointA.y;
        float32 z2 = pointC.z - pointA.z;
        
        float32 s1 = uvB.u - uvA.u;
        float32 t1 = uvB.v - uvA.v;

        float32 s2 = uvC.u - uvA.u;
        float32 t2 = uvC.v - uvA.v;

        float32 r = 1.0f;
        float32 d = (s1 * t2 - s2 * t1);
        if (fabsf(d) > m::c::floatEpsilon)
        {
            r = 1.0f / d;
        }

        m::vector3d sdir(
            (t2 * x1 - t1 * x2) * r, 
            (t2 * y1 - t1 * y2) * r,
            (t2 * z1 - t1 * z2) * r
            );

        m::vector3d tdir(
            (s1 * x2 - s2 * x1) * r, 
            (s1 * y2 - s2 * y1) * r,
            (s1 * z2 - s2 * z1) * r
            );

        *sVector = sdir;
        *tVector = tdir;
    }
}

inline void orthonormalizeTangentVector(
    const m::vector3d& normal,
    const m::vector3d& sVector,
    const m::vector3d& tVector,
    m::vector4d* tangent
    )
{
    if (tangent != NULL)
    {
        m::vector3d n(normal);
        m::vector3d s1(sVector);
        m::vector3d s2;
        m::vector3d delta;

        // orthonormalize tangent
        n.normalize();
        n.scale(n.dot(s1), &delta);
        s1.subtract(delta, &s2);

        m::vector3d normalizedTangent;
        s2.normalize(&normalizedTangent);

        // encode w component of tangent vector with scale factor,
        // used to scale binormal in the HLSL to account for handedness
        m::vector3d binormal;
        ((m::vector3d)normal).cross(sVector, &binormal);
        float32 dot = binormal.dot(tVector);
        float32 handednessAdjustment = (dot < 0.0f ? -1.0f : 1.0f);

        // set output tangent vector
        tangent->x = normalizedTangent.x;
        tangent->y = normalizedTangent.y;
        tangent->z = normalizedTangent.z;
        tangent->w = handednessAdjustment;
    }
}

inline void computeTangentVectorsForVertices(
    vertex_lit_colored_textured* vertices,
    const uint32 totalVerts,
    const uint16* indices,
    const uint32 totalIndices
    )
{
    if (vertices != NULL && totalVerts > 0 && indices != NULL && totalIndices >= 3)
    {
        // treat the indices as a triangle list
        for (uint32 i = 0; i < totalIndices; i += 3)
        {
            uint16 a = indices[i];
            uint16 b = indices[i+1];
            uint16 c = indices[i+2];

            if (a < totalVerts && b < totalVerts && c < totalVerts)
            {
                m::vector3d sVector;
                m::vector3d tVector;

                computeTangentVector(
                    vertices[a].position,
                    vertices[b].position,
                    vertices[c].position,
                    vertices[a].uv[0],
                    vertices[b].uv[0],
                    vertices[c].uv[0],
                    &sVector,
                    &tVector
                    );

                orthonormalizeTangentVector(
                    vertices[a].normal,
                    sVector,
                    tVector,
                    &vertices[a].tangent
                    );

                orthonormalizeTangentVector(
                    vertices[b].normal,
                    sVector,
                    tVector,
                    &vertices[b].tangent
                    );

                orthonormalizeTangentVector(
                    vertices[c].normal,
                    sVector,
                    tVector,
                    &vertices[c].tangent
                    );
            }
        }
    }
}

}}}} // namespace baja.graphics.dx11_1.details